package com.ecr.hub.service.data.input.xls.impl;

import java.io.FileInputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.Resource;

import org.apache.log4j.Logger;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import com.ecr.hub.ApplicationException;
import com.ecr.hub.DaoApplicationException;
import com.ecr.hub.DestinationUnknownException;
import com.ecr.hub.SillonAlreadyProcededException;
import com.ecr.hub.SillonNotFoundException;
import com.ecr.hub.SillonTypeException;
import com.ecr.hub.XlsParameterException;
import com.ecr.hub.model.HubInputData;
import com.ecr.hub.model.composition.Composition;
import com.ecr.hub.model.destination.DestinationRailTranscoder;
import com.ecr.hub.model.enumeration.HubDataInputStatus;
import com.ecr.hub.model.enumeration.HubLading;
import com.ecr.hub.model.enumeration.HubSillonStatus;
import com.ecr.hub.model.enumeration.HubSillonType;
import com.ecr.hub.model.enumeration.HubTypeFile;
import com.ecr.hub.model.sillon.Sillon;
import com.ecr.hub.model.wagon.Wagon;
import com.ecr.hub.service.data.input.IInputDataManager;
import com.ecr.hub.service.data.input.xls.IXlsDecoder;
import com.ecr.hub.service.destination.IDestinationRailTranscoder;
import com.ecr.hub.service.hub.IHubManager;
import com.google.common.collect.Lists;

@Component("xlsDecoder")
public class XlsDecoderImpl implements IXlsDecoder {

	private static Logger log = Logger.getLogger(XlsDecoderImpl.class);

	// Ressource pour la table des données.
	@Resource(name = "inputDataManager")
	IInputDataManager inputDataManager;

	// Ressource l'intégration des sillons et des compositions.
	@Resource(name = "hubManager")
	IHubManager hubManager;

	// Ressource pour la liste des voies / destinations.
	@Resource(name = "destinationRailTranscoder")
	IDestinationRailTranscoder destinationRailTranscoder;

	// Les variables pour le fonctionnement du décodeur.
	@Value(value = "${xls.worksheet.sillon.name}")
	private String xlsWorksheetSillonName;

	@Value(value = "${xls.worksheet.compo.name}")
	private String xlsWorksheetCompositionName;

	@Value(value = "${repository.folder}")
	private String repositoryFolder;

	@Value(value = "${xls.date.format:dd/MM/yyyy}")
	private String xlsDateFormat;

	/**
	 * Méthode principale qui va vérifier dans la base de données s'il y a des
	 * fichiers à décoder. Lance le décodage des fichiers en fonction du type de
	 * fichier (sillon ou composition).
	 */
	@Override
	public void decodeXls() {

		// Liste des fichiers à traiter.
		List<HubInputData> lstHubInputDatas = null;

		try {
			// On recupère tout les données dont le status est à FILE_VALID
			lstHubInputDatas = inputDataManager
					.findByStatus(HubDataInputStatus.FILE_VALID);

		} catch (DaoApplicationException e) {
			log.fatal("Hub : Impossible de se connecter à la table"
					+ " hub_input_data pour le retrait des fichiers à charger : "
					+ e.getMessage());

		} catch (ApplicationException e) {
			log.fatal("Hub : Impossible de se connecter à la table"
					+ " hub_input_data pour le retrait des fichiers à charger : "
					+ e.getMessage());
		}

		// Si on a pu récupérer une liste (même vide).
		if (lstHubInputDatas != null) {
			// On parcoure la liste des fichiers à traiter (des sillons ou des
			// relevés de composition)
			for (HubInputData inputData : lstHubInputDatas) {
				try {
					// Le fichier est il un fichier de sillons ?
					if (isXlsSillon(inputData)) {
						analyseSillon(inputData);

						// Le fichier est il un fichier de compositions.
					} else if (isXlsComposition(inputData)) {
						analyseComposition(inputData);

						// Le fichier n'est pas valide.
					} else {
						inputData
								.setStatus(HubDataInputStatus.ERROR_XLS_BAD_FORMAT);
						inputData.setErrorMessage("Fichier non trouvé");
						log.fatal("Fichier non trouvé.");
					}
				} catch (SillonTypeException e) {
					inputData
							.setStatus(HubDataInputStatus.ERROR_XLS_BAD_FORMAT);

					inputData.setErrorMessage(e.getMessage());
					log.error("Erreur sillon lors de l'analyse : "
							+ e.getMessage());

				} catch (DestinationUnknownException e) {
					inputData
							.setStatus(HubDataInputStatus.ERROR_XLS_BAD_DESTINATION);

					inputData.setErrorMessage(e.getMessage());
					log.error("Erreur destination lors de l'analyse : "
							+ e.getMessage());

				} catch (SillonNotFoundException e) {
					inputData
							.setStatus(HubDataInputStatus.ERROR_XLS_BAD_SILLON);

					inputData.setErrorMessage("");
					log.error("Erreur sillon lors de l'analyse : "
							+ e.getMessage());

				} catch (SillonAlreadyProcededException e) {
					inputData
							.setStatus(HubDataInputStatus.ERROR_XLS_BAD_SILLON_PLAN);

					inputData.setErrorMessage(e.getMessage());
					log.error("Erreur sillon lors de l'analyse : "
							+ e.getMessage());

				} catch (DaoApplicationException e) {
					inputData
							.setStatus(HubDataInputStatus.ERROR_XLS_DAO_ACCESS_EXCEPTION);

					inputData.setErrorMessage(e.getMessage());
					log.fatal("Erreur dao lors de l'analyse : "
							+ e.getMessage());

				} catch (XlsParameterException e) {
					inputData
							.setStatus(HubDataInputStatus.ERROR_XLS_BAD_FORMAT);

					inputData.setErrorMessage(e.getMessage());
					log.error("Erreur de paramètre lors de l'analyse : "
							+ e.getMessage());

				} catch (Exception e) {
					inputData
							.setStatus(HubDataInputStatus.ERROR_XLS_BAD_FORMAT);

					inputData.setErrorMessage(e.getMessage());
					log.fatal("Erreur lors de l'analyse : " + e.getMessage());

				} finally {
					try {
						// Mise à jour du statut traitement dans la table
						// hub_input_data.
						inputDataManager.update(inputData);
					} catch (ApplicationException e) {
						log.fatal("Erreur lors de l'analyse, impossible "
								+ "de mettre à jour la table hub_input_data : "
								+ e.getMessage());
					}
				}
			}
		}
	}

	/**
	 * Indique si le fichier est un fichier de sillons.
	 */
	private boolean isXlsSillon(HubInputData inputData) {

		return (inputData.getFileStoreName()
				.contains(IXlsDecoder.FILE_PREFIX_SILLONS));
	}

	/**
	 * Indique si le fichier est un fichier de compositions.
	 */
	private boolean isXlsComposition(HubInputData inputData) {

		return (inputData.getFileStoreName()
				.contains(IXlsDecoder.FILE_PREFIX_COMPOSITIONS));
	}

	/**
	 * Analyse du fichier de composition. Pour ce fichier tous les champs sont
	 * en format texte.
	 */
	private void analyseComposition(HubInputData inputData) throws Exception {

		log.info("Analyse du fichier de compositions : "
				+ inputData.getFileStoreName());

		// Pattern de véification pour les wagons.
		Pattern p = Pattern.compile(REGEX_WAGON);

		HSSFWorkbook workbook = new HSSFWorkbook(new FileInputStream(
				repositoryFolder + inputData.getFileStoreName()));
		HSSFSheet worksheet = workbook.getSheet(xlsWorksheetCompositionName);

		// Vérifie que l'on a bien l'onglet.
		if (worksheet == null)
			throw new ApplicationException(
					"La feuille (onglet) n'a pas été trouvée.");

		// Initialisation.
		int numLot = 0;
		int posNum = 0;
		int railId = 0;
		int prevRailId = 0;
		Composition composition;

		// On crée une liste de compositions.
		List<Composition> lstCompositions = Lists.newArrayList();

		// Données pour faire la liaison avec le sillon.
		String tcmsTrainId;

		// Lecture du TCMS TRAIN ID.
		HSSFRow compositionRow = worksheet
				.getRow(COMPOSITION_LINE_TCMS_TRAIN_ID);
		HSSFCell cellTcmsTrainId = compositionRow
				.getCell(COMPOSITION_COLUMN_TCMS_TRAIN_ID);
		if (cellTcmsTrainId != null) {
			tcmsTrainId = cellTcmsTrainId.getStringCellValue().trim()
					.toUpperCase();
		} else {
			throw new Exception("Tcms Train Id nul.");
		}

		// Parcourt du fichier xls pour recupérer la liste des compositions
		int currentLine = COMPOSITION_LINE_TITLE + 1;
		HSSFRow currentRow = worksheet.getRow(currentLine);

		while (currentRow != null) {

			composition = new Composition(new Wagon());
			// Insere la position du wagon dans la composition.
			composition.getWagon().getCurrentPos().setPosNum(posNum);

			// wagonExtId
			HSSFCell cellWagonExtId = currentRow.getCell(COMPOSITION_COLUMN_ID);
			if (cellWagonExtId != null) {
				String strWagonExtId = cellWagonExtId.getStringCellValue()
						.trim();
				// S'il y a des blancs, on les supprime.
				if (strWagonExtId.indexOf(" ") != -1)
					strWagonExtId = strWagonExtId.replaceAll("\\s+", "");
				composition.getWagon().setExtId(strWagonExtId);
			} else {
				throw new XlsParameterException(
						"Identifiant wagon null, ligne : " + (currentLine + 1));
			}

			// Véification du format pour l'identifiant du wagon.
			Matcher m = p.matcher(composition.getWagon().getExtId());
			if (!m.matches())
				throw new XlsParameterException(
						"Identifiant wagon au mauvais format, ligne : "
								+ (currentLine + 1));

			// lading (on fait la transco avec TCMS.)
			HSSFCell cellLading = currentRow.getCell(COMPOSITION_COLUMN_LADING);
			if (cellLading != null) {
				String strLading = cellLading.getStringCellValue()
						.toUpperCase().trim();
				if (INDICATOR_LOADED.equals(strLading)) {
					composition.getWagon().setLading(
							HubLading.LOADED.getValue());
				} else if (INDICATOR_EMPTY.equals(strLading)) {
					composition.getWagon()
							.setLading(HubLading.EMPTY.getValue());
				} else {
					throw new XlsParameterException(
							"Indicateur chargé/vide au mauvais format, ligne : "
									+ (currentLine + 1));
				}
			} else {
				throw new XlsParameterException(
						"Indicateur chargé/vide null, ligne : "
								+ (currentLine + 1));
			}

			// destinationExtId (Il faut aller la chercher dans le flow
			// description !!!!).
			HSSFCell cellDestinationExtId = currentRow
					.getCell(COMPOSITION_COLUMN_FLOW_DESCRIPTION);
			if (cellDestinationExtId != null) {
				String strDestinationExtId = cellDestinationExtId
						.getStringCellValue().trim();
				// Présence ou non du tiret (toujours pour TCMS) jamais pour
				// chargement manuel.....
				if (strDestinationExtId.indexOf(DESTINATION_EXT_ID_SEPARATOR) != -1) {
					strDestinationExtId = strDestinationExtId.substring(0,
							strDestinationExtId
									.indexOf(DESTINATION_EXT_ID_SEPARATOR));
					composition.setDestinationExtId(strDestinationExtId.trim());
				} else {
					composition.setDestinationExtId(strDestinationExtId.trim());
				}
			} else {
				throw new XlsParameterException(
						"Identifiant destination null, ligne : "
								+ (currentLine + 1));
			}

			// Le poids de chargement.
			HSSFCell cellLadingWeight = currentRow
					.getCell(COMPOSITION_COLUMN_N_WEIGHT);
			if (cellLadingWeight != null) {
				Float fLadingWeight = Float.valueOf(cellLadingWeight
						.getStringCellValue());
				composition.getWagon().setWeightLading(fLadingWeight);
			} else {
				throw new XlsParameterException(
						"Poids de chargement null, ligne : "
								+ (currentLine + 1));
			}

			// Ate
			HSSFCell cellAte = currentRow.getCell(COMPOSITION_COLUMN_ATE);
			if (cellAte != null) {
				String strAte = cellAte.getStringCellValue();
				// Transco rapide si en anglais (21/11/2011).
				if ("Yes".equalsIgnoreCase(strAte)) {
					strAte = "Oui";
				} else if ("No".equalsIgnoreCase(strAte)) {
					strAte = "Non";
				}
				composition.getWagon().setAte(strAte);
			} else {
				throw new XlsParameterException("Ate null, ligne : "
						+ (currentLine + 1));
			}

			// Le brake force.
			HSSFCell cellBrakeForce = currentRow
					.getCell(COMPOSITION_COLUMN_BRAKE_FORCE);
			if (cellBrakeForce != null) {
				composition.getWagon().setBrakeForce(
						Float.valueOf((cellBrakeForce.getStringCellValue())));
			} else {
				throw new XlsParameterException("Masse freinée nulle, ligne : "
						+ (currentLine + 1));
			}

			// Récupération de la voie pour la destination.
			DestinationRailTranscoder transcoder = destinationRailTranscoder
					.getRailForDestination(composition.getDestinationExtId());
			composition.getWagon().getCurrentPos()
					.setPosRailId(transcoder.getRailId());

			// Ceci est la destination finale du wagon qui va le suivre dans
			// tout le hub, même si le wagon change de voie.
			composition.getWagon().setDestinationId(
					transcoder.getDestinationId());

			// Calcul du lot. On fait ce calcul en fonction de la voie et non
			// de l'id de destination car plusieurs destinations peuvent aller
			// sur la même voie.
			railId = composition.getWagon().getCurrentPos().getPosRailId();
			if (prevRailId != railId) {
				prevRailId = railId;
				numLot++;
			}

			// Insertion du numéro de lot.
			composition.getWagon().setNumLot(numLot);

			// On copie la voie dans le champ de sauvegarde.
			composition.setPosRailSaveId(transcoder.getRailId());

			// Ajout de la ligne de composition dans la liste des compositions.
			lstCompositions.add(composition);
			posNum++;

			// Passe à la ligne suivante.
			currentRow = worksheet.getRow(++currentLine);
		}
		// Copie en base de la liste des relevés de composition.
		hubManager.integrateComposition(lstCompositions, tcmsTrainId,
				inputData.getFileStoreName());

		// Passe le statut à "Fichié traité".
		inputData.setStatus(HubDataInputStatus.CLOSE);
	}

	/**
	 * Analyse du fichier des sillons. Pour ce fichier tous les champs sont en
	 * format texte.
	 */
	private void analyseSillon(HubInputData inputData) throws Exception {

		log.info("Analyse du fichier de sillon(s) : "
				+ inputData.getFileStoreName());

		// Pattern de véification pour les heures.
		Pattern p = Pattern.compile(REGEX_TIME);

		// Formateur pour les dates.
		SimpleDateFormat formatter = new SimpleDateFormat(xlsDateFormat);

		HSSFWorkbook workbook = new HSSFWorkbook(new FileInputStream(
				repositoryFolder + inputData.getFileStoreName()));
		HSSFSheet worksheet = workbook.getSheet(xlsWorksheetSillonName);

		// Vérifie que l'on a bien la feuille.
		if (worksheet == null)
			throw new ApplicationException(
					"La feuille (onglet) n'a pas été trouvée.");

		Sillon sillon = null;
		int cellMovementDate = 0;
		int cellMovementTime = 0;
		int currentLine = START_LINE_SILLON;

		List<Sillon> lstSillons = Lists.newArrayList();

		HSSFRow currentRow = worksheet.getRow(currentLine);
		while (currentRow != null) {

			// Création objet sillon.
			sillon = new Sillon();
			sillon.setStatusId(HubSillonStatus.ATTRIBUTED.getValue());

			// Sillon_id.
			HSSFCell cellExtId = currentRow.getCell(SILLON_COLUMN_SILLON_ID);
			if (cellExtId != null) {
				String strExtId = cellExtId.getStringCellValue().trim();
				// S'il y a des blancs, on les supprime.
				if (strExtId.indexOf(" ") != -1)
					strExtId = strExtId.replaceAll("\\s+", "");
				sillon.setExtId(strExtId.trim());
			} else {
				throw new XlsParameterException("Id externe null, ligne : "
						+ (currentLine + 1));
			}

			// Tcms_train_id (on prend nul...).
			HSSFCell cellTcmsTrainId = currentRow
					.getCell(SILLON_COLUMN_TCMS_TRAIN_ID);
			if (cellTcmsTrainId != null) {
				sillon.setTcmsTrainId(cellTcmsTrainId.getStringCellValue()
						.trim().toUpperCase());
			}

			// Origine. (on en profite pour positionner les colonnes dates et
			// heure.)
			HSSFCell cellOrigin = currentRow.getCell(SILLON_COLUMN_ORIGIN);
			if (cellOrigin != null) {
				sillon.setOrigin(cellOrigin.getStringCellValue().trim()
						.toUpperCase());
			} else {
				throw new XlsParameterException("Origine nulle, ligne : "
						+ (currentLine + 1));
			}

			// Destination.
			HSSFCell cellDestination = currentRow
					.getCell(SILLON_COLUMN_DESTINATION);
			if (cellDestination != null) {
				sillon.setDestination(cellDestination.getStringCellValue()
						.trim().toUpperCase());
			} else {
				throw new XlsParameterException("Destination nulle, ligne : "
						+ (currentLine + 1));
			}

			// On enregistre le type du sillon (départ / arrivée) et on en
			// profite pour positionner les cellules pour la recherche de la
			// date et de l'heure du mouvement).
			if (sillon.getOrigin().contains(HUB_STATION)) {
				sillon.setType(HubSillonType.DEPARTURE.getValue());

				cellMovementDate = SILLON_COLUMN_DEP_DATE;
				cellMovementTime = SILLON_COLUMN_DEP_TIME;
			} else if (sillon.getDestination().contains(HUB_STATION)) {
				sillon.setType(HubSillonType.ARRIVAL.getValue());

				cellMovementDate = SILLON_COLUMN_ARR_DATE;
				cellMovementTime = SILLON_COLUMN_ARR_TIME;
			} else {
				throw new SillonTypeException(
						"Impossible de déterminer le type du sillon (Arrivée / Départ), ligne : "
								+ (currentLine + 1));
			}

			// Selon que le sillon soit un sillon de départ ou d'arrivée, on
			// cherche la date et l'heure dans différentes cellules.

			// Date.
			HSSFCell cellMoveDate = currentRow.getCell(cellMovementDate);
			if (cellMoveDate != null) {
				sillon.setMovementDate(formatter.parse(cellMoveDate
						.getStringCellValue()));
			} else {
				throw new XlsParameterException(
						"Date de mouvement nulle, ligne : " + (currentLine + 1));
			}

			// Heure.
			HSSFCell cellMoveTime = currentRow.getCell(cellMovementTime);
			if (cellMoveTime != null) {
				sillon.setMovementTime(cellMoveTime.getStringCellValue());
			} else {
				throw new XlsParameterException(
						"Heure de mouvement nulle, ligne : "
								+ (currentLine + 1));
			}

			// Vérification du format de l'heure.
			Matcher m = p.matcher(sillon.getMovementTime());
			if (!m.matches())
				throw new XlsParameterException(
						"Heure de mouvement au mauvais format, ligne : "
								+ (currentLine + 1));

			// Client du sillon.
			sillon.setEcrOwner(false);
			HSSFCell cellOwner = currentRow.getCell(SILLON_COLUMN_CUSTOMER);
			if (cellOwner != null) {
				String strOwner = cellOwner.getStringCellValue();
				if (strOwner.contains(ECR_CUSTOMER))
					sillon.setEcrOwner(true);
			} else {
				throw new XlsParameterException(
						"Propriétaire du sillon nul, ligne : "
								+ (currentLine + 1));
			}

			// On copie les dates et heures théoriques dans les champ pour les
			// dates et heures réelles.
			sillon.setRealMovementDate(sillon.getMovementDate());
			sillon.setRealMovementTime(sillon.getMovementTime());

			// Nombre de wagons.
			HSSFCell cellNbWagons = currentRow
					.getCell(SILLON_COLUMN_WAGON_CONVEYED);
			if (cellNbWagons != null) {
				try {
					sillon.setNbWagons(Integer.parseInt(cellNbWagons
							.getStringCellValue()));
				} catch (NumberFormatException e) {
					sillon.setNbWagons(0);
				}
			}

			// Y a t'il une annulation du sillon ?
			HSSFCell cellCancel = currentRow
					.getCell(SILLON_COLUMN_CANCELLATION);
			if (cellCancel != null
					&& cellCancel.getStringCellValue().trim().length() > 0) {
				sillon.setStatusId(HubSillonStatus.CANCELED.getValue());
				sillon.setComment(CANCELLED + " : "
						+ cellCancel.getStringCellValue());
			}

			// On ajoute le sillon dans la liste et on incrémente le numéro de
			// ligne courante.
			currentRow = worksheet.getRow(++currentLine);

			// Intégration du sillon (sauf si aucun TCMS ID).
			if (sillon.getTcmsTrainId() != null
					&& sillon.getTcmsTrainId().trim().length() > 0)
				lstSillons.add(sillon);
		}
		// Copie en base de la liste des sillons
		hubManager.integrateSillons(lstSillons);

		// Passe le statut à "Fichier traité".
		inputData.setStatus(HubDataInputStatus.CLOSE);
	}

	/**
	 * Construction d'un nom de fichier propre à l'application en fonction du
	 * type de fichier (sillon ou composition). Le format est
	 * [sillons/compositions]_yyyyMMdd-HHmmss-SSS.xls
	 */
	public String getStoreFileNameXls(HubTypeFile typeFile) {

		StringBuffer fileName = new StringBuffer();
		DateFormat df = new SimpleDateFormat("yyyyMMdd-HHmmss-SSS");

		if (HubTypeFile.SILLON.equals(typeFile)) {
			fileName.append(IXlsDecoder.FILE_PREFIX_SILLONS);
		} else {
			fileName.append(IXlsDecoder.FILE_PREFIX_COMPOSITIONS);
		}
		fileName.append("-");
		fileName.append(df.format(new Date())).append(IXlsDecoder.EXCEL_SUFFIX);
		return fileName.toString();
	}
}